<?php

/**
 * Ignus Framework
 *
 * @author Sergey Palyanov <sergey.palyanov@gmail.com>
 * @link http://code.google.com/p/ignus-php/
 * @license http://apache.org/licenses/LICENSE-2.0
 * @package Db
 * @subpackage Mongodb
 * @since 0.3
 */

namespace Ignus\Db\Mongodb;

use Ignus\App;
use Ignus\Db\Driver;

use Ignus\Log\LogManager;
use Ignus\Log\Logger;

/**
 * @package Db
 * @subpackage Mongodb
 * @since 0.3
 */
class Mongodb implements Driver
{
    const META_COLLECTION = '_ignus';

    /**
     * @var object \Mongo object
     */
    private $mongo;

    /**
     * @var \MongoDB object
     */
    private $db;

    /**
     * True to use safe commands
     * @var boolean
     */
    private $safe;

    /**
     * @param string $dsn Connection string
     * @param array $params Connection options
     * @return void
     */
    public function connect($dsn, array $params)
    {
        try {
            $db = trim(parse_url($dsn, PHP_URL_PATH), '/');
            $this->mongo = new \Mongo($dsn, $params);
            $this->db = $this->mongo->selectDB($db);
            $this->safe = !App::isProduction();
        }
        catch (\MongoConnectionException $e) {
            throw new \RuntimeException("Error while connecting to  '$dsn' at ".__CLASS__, null, $e);
        }
    }

    /**
     * Returns MongoQuery object to query data from collection
     * @param string $collectionName Collection name
     * @param array $query Query
     * @return MongoQuery
     */
    public function find($collectionName, array $query = array())
    {
        return new MongoQuery($this->db->selectCollection($collectionName), $query);
    }

    /**
     * Generate next unique entity identifier
     * @param string $collection Table or collection of entities
     * @return mixed
     * @since 0.5.4
     */
    public function generateId($collection)
    {
        $command = array(
            'findAndModify' =>  self::META_COLLECTION,
            'query'         =>  array('_id' => 'generator'),
            'update'        =>  array('$inc' => array($collection => 1)),
            'new'           =>  true,
            'upsert'        =>  true,
        );

        $result = $this->command($command);
        if (!isset($result['value'][$collection]))
            throw new \RuntimeException("Error generating id");

        return $result['value'][$collection];
    }

    /**
     * Returns document with specified id
     * @param string $collectionName Collection name
     * @param mixed $id Document id
     * @return array|null
     */
    public function getById($collectionName, $id)
    {
        $query = new MongoQuery($this->db->selectCollection($collectionName),  array('_id' => $id));
        return $query->getOne();
    }

    /**
     * Insert new document $object to collection $collectionName
     * @param string $collectionName Collection name
     * @param array $object Object to insert
     * @return boolean|string False on failure, document id on success
     */
    public function insert($collectionName, array $object)
    {
        try {
            $collection = $this->db->selectCollection($collectionName);
            $result = $collection->insert($object, array('safe' => $this->safe));
            if (!is_array($result) || !$result['ok'])
                return false;

            return $object['_id'];
        }
        catch (\MongoCursorException $e) {
            LogManager::getLogger(__CLASS__)->error("Error inserting document to $collectionName: $e");
            return false;
        }
    }

    /**
     * Updates documents based on a given criteria.
     * Returns true on success and false on failure
     * @param string $collectionName Collection name
     * @param array $query Criteria
     * @param array $object Document
     * @param array $options Operation options
     * @return boolean
     */
    public function update($collectionName, array $query, array $object, array $options = array())
    {
        $options += array(
            'multiple'  =>  false,
            'safe'      =>  $this->safe,
        );

        try {
            $collection = $this->db->selectCollection($collectionName);
            return $collection->update($query, $object, $options);
        }
        catch (\MongoCursorException $e) {
            LogManager::getLogger(__CLASS__)->error("Error updating document: $e");
            return false;
        }
    }

    /**
     * Updates document with id $id
     * @param string $collectionName Collection name
     * @param string $id Document identifier
     * @param array $object Document
     * @param array $options Operation options
     * @return boolean
     */
    public function updateById($collectionName, $id, array $object, array $options = array())
    {
        return $this->update($collectionName, array('_id' => $id), $object, $options);
    }

    /**
     * Updates documents on a given criteria, and creates new document if no documents matched criteria.
     * Returns true on success and false on failure
     * @param string $collectionName Collection name
     * @param array $query Criteria
     * @param array $object Document
     * @param array $options Operation options
     * @return boolean
     */
    public function upsert($collectionName, array $query, array $object, array $options = array())
    {
        $options['upsert'] = true;
        return $this->update($collectionName, $query, $object, $options);
    }

    /**
     * Execute database command
     * @param array $params Command parameters
     * @return array Result
     * @since 0.5.2
     */
    public function command(array $params)
    {
        return $this->db->command($params);
    }

    /**
     * Execute aggregate function on collection
     * @param string $collectionName Collection name
     * @return MongoGroup
     * @since 0.5.6
     */
    public function group($collectionName)
    {
        return new MongoGroup($this->db->selectCollection($collectionName));
    }
}
